/****************************************************
	文件：NetManager.cs
	作者：HuskyT
	邮箱：1005240602@qq.com
	日期：2020/4/26 21:18:25
	功能：网络服务管理类
*****************************************************/

using System;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.Threading;
using HTFW.Core;
using HTFW.Utility.Global;
using HTServer.Proto;

namespace HTFW.Net
{
    public class NetManager : Singleton<NetManager>, IManager
    {
        #region NetEvent（供外部注册，内部调用）

        /// <summary>
        /// 网络事件
        /// </summary>
        public enum NetEvent
        {
            /// <summary>
            /// 连接服务器成功
            /// </summary>
            ConnectSuccess,

            /// <summary>
            /// 连接服务器失败
            /// </summary>
            ConnectFailure,

            /// <summary>
            /// 关闭与服务器的连接
            /// </summary>
            Close,
        }

        /// <summary>
        /// 网络事件委托
        /// </summary>
        public delegate void NetEventListenner(string str);

        /// <summary>
        /// 缓存网络事件监听的字典
        /// </summary>
        private Dictionary<NetEvent, NetEventListenner> mNetEventDict = new Dictionary<NetEvent, NetEventListenner>();

        public void AddEventListenner(NetEvent netEvent, NetEventListenner eventListenner)
        {
            if (mNetEventDict.ContainsKey(netEvent))
            {
                mNetEventDict[netEvent] += eventListenner;
            }
            else
            {
                mNetEventDict[netEvent] = eventListenner;
            }
        }

        public void RemoveEventListenner(NetEvent netEvent, NetEventListenner eventListenner)
        {
            if (mNetEventDict.ContainsKey(netEvent))
            {
                mNetEventDict[netEvent] -= eventListenner;
                if (mNetEventDict[netEvent] == null)
                {
                    mNetEventDict.Remove(netEvent);
                }
            }
        }

        /// <summary>
        /// 执行网络事件
        /// </summary>
        private void FirstEvent(NetEvent netEvent, string str)
        {
            if (mNetEventDict.ContainsKey(netEvent))
            {
                mNetEventDict[netEvent](str);
            }
        }

        #endregion

        #region Protocol（消息分发，共外部添加网络协议监听）

        /// <summary>
        /// 网络协议的监听
        /// </summary>
        public delegate void ProtoListenner(MsgBase msg);

        private Dictionary<ProtoEnum, ProtoListenner> mProtoDict = new Dictionary<ProtoEnum, ProtoListenner>();

        /// <summary>
        /// 添加网络协议的监听（一一对应，一个协议对应一个监听）
        /// </summary>
        /// <param 协议枚举="protocol"></param>
        /// <param 协议监听="listenner"></param>
        public void AddProtoListenner(ProtoEnum protocol, ProtoListenner listenner)
        {
            mProtoDict[protocol] = listenner;
        }

        /// <summary>
        /// 执行协议监听
        /// </summary>
        private void FirstProto(ProtoEnum protocol, MsgBase msg)
        {
            if (mProtoDict.ContainsKey(protocol))
            {
                mProtoDict[protocol](msg);
            }
        }

        #endregion

        /// <summary>
        /// 公钥
        /// </summary>
        public string PublicKey = "HuskyTGame.Github.IO";

        /// <summary>
        /// 秘钥
        /// </summary>
        public string SecretKey { get; private set; }

        private Socket mSocket;
        private ByteArray mReadBuff;
        private string mIp;
        private int mPort;

        /// <summary>
        /// 正在连接服务器
        /// </summary>
        private bool mIsConnecting;

        /// <summary>
        /// 正在关闭服务器连接
        /// </summary>
        private bool mIsClosing;

        /// <summary>
        /// 消息队列
        /// </summary>
        private Queue<MsgBase> mMsgQue;

        /// <summary>
        /// 待处理消息个数
        /// </summary>
        private int mMsgCount;

        /// <summary>
        /// Unity中执行的消息队列
        /// </summary>
        private Queue<MsgBase> mUnityMsgQue;

        /// <summary>
        /// 发送消息时的写入队列
        /// </summary>
        private Queue<ByteArray> mWriteQue;

        /// <summary>
        /// 总体处理消息的线程
        /// </summary>
        private Thread mMsgThread;

        /// <summary>
        /// 处理心跳包的线程
        /// </summary>
        private Thread mHeartThread;

        /// <summary>
        /// 最后一次上传心跳包消息的时间（服务器接收到心跳包的时间，由服务器记录并返回客户端）
        /// </summary>
        private static long mLastPingTime;

        /// <summary>
        /// 最后一次接收心跳包消息的时间
        /// </summary>
        private static long mLastPongTime;

        /// <summary>
        /// 心跳包发送时间间隔
        /// </summary>
        private static long mPingTimeInterval = 30;

        /// <summary>
        /// 心跳包超时比例（超过mPingTimeoutRatio个心跳包时间间隔就被视为连接超时）
        /// </summary>
        private static long mPingTimeoutRatio = 4;

        /// <summary>
        /// 是否连接服务器成功过（初始为false，连接成功过一次后设置为true）
        /// </summary>
        private bool mIsConnectedSuccess;

        /// <summary>
        /// 是否断线
        /// </summary>
        private bool mIsBreakConnect;

        /// <summary>
        /// 是否重新连接（用于重连后的功能）
        /// </summary>
        private bool mIsReconnect;

        /// <summary>
        /// 连接服务器
        /// </summary>
        public void Connect(string ip, int port)
        {
            if (mSocket != null && mSocket.Connected)
            {
                Debug.LogError("连接服务器失败，服务器已经连接！");
                return;
            }

            if (mIsConnecting)
            {
                Debug.LogError("服务器连接失败，正在连接服务器中，请勿重复连接！");
                return;
            }

            InitState(); //初始化变量
            mSocket.NoDelay = true;
            mIsConnecting = true;
            mSocket.BeginConnect(ip, port, ConnectedCallBack, mSocket);
            mIp = ip;
            mPort = port;
        }


        public void Init(params object[] args)
        {
        }

        public void Dispose()
        {
        }

        /// <summary>
        /// 初始化变量
        /// </summary>
        private void InitState()
        {
            mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            mReadBuff = new ByteArray();
            mIsConnecting = false;
            mIsClosing = false;
            mMsgQue = new Queue<MsgBase>();
            mUnityMsgQue = new Queue<MsgBase>();
            mWriteQue = new Queue<ByteArray>();
            mLastPingTime = GetTimeStamp();
            mLastPongTime = GetTimeStamp();
            mIsConnectedSuccess = false;
            mIsBreakConnect = false;
            mIsReconnect = false;
            if (mIsReconnect)
            {
            } //无用，防警告的
        }

        /// <summary>
        /// 帧循环
        /// </summary>
        public void Update()
        {
            if (mIsBreakConnect && mIsConnectedSuccess) //断线重连
            {
                Reconnect();
                mIsBreakConnect = false;
            }

            MsgUpdate();
        }

        /// <summary>
        /// 重新连接
        /// </summary>
        private void Reconnect()
        {
            Connect(mIp, mPort);
            mIsReconnect = true;
        }

        /// <summary>
        /// 处理Unity消息的帧循环（所有Unity的网络消息分发在此处执行）
        /// </summary>
        private void MsgUpdate()
        {
            if (mSocket == null || mSocket.Connected == false) return;
            if (mMsgCount == 0) return;
            MsgBase msg = null;
            lock (mUnityMsgQue)
            {
                if (mUnityMsgQue.Count > 0)
                {
                    msg = mUnityMsgQue.Dequeue();
                    mMsgCount--;
                }
            }

            if (msg != null)
            {
                FirstProto(msg.ProtoType, msg); //执行协议，分发消息
            }
        }

        /// <summary>
        /// 连接服务器回调
        /// </summary>
        private void ConnectedCallBack(IAsyncResult ar)
        {
            try
            {
                Socket socket = (Socket) ar.AsyncState;
                socket.EndConnect(ar);
                mIsConnecting = false;
                mIsConnectedSuccess = true;
                FirstEvent(NetEvent.ConnectSuccess, "");
                Debug.Log("连接服务器成功");
                //开启总体处理消息的线程
                mMsgThread = new Thread(MsgThread);
                mMsgThread.IsBackground = true;
                mMsgThread.Start();
                //开启心跳包接收线程
                mHeartThread = new Thread(HeartThread);
                mHeartThread.IsBackground = true;
                mHeartThread.Start();
                //请求秘钥
                ProtoManager.SecretRequest();
                //异步接收消息
                socket.BeginReceive(mReadBuff.Bytes, mReadBuff.WriteIndex, mReadBuff.Remain, 0, ReceivedCallBack,
                    socket);
            }
            catch (SocketException e)
            {
                Debug.LogError("连接服务器失败：" + e.ToString());
                mIsConnecting = false;
            }
        }

        /// <summary>
        /// 接收消息回调
        /// </summary>
        private void ReceivedCallBack(IAsyncResult ar)
        {
            try
            {
                Socket socket = (Socket) ar.AsyncState;
                int count = socket.EndReceive(ar); //接收到的数据的长度
                if (count <= 0)
                {
                    //关闭与服务器的连接
                    Close();
                    return;
                }

                mReadBuff.WriteIndex += count; //移动写入索引
                //处理接收到的数据包（处理分包粘包产生的问题）
                OnReceiveData();
                if (mReadBuff.Remain < 8)
                {
                    mReadBuff.MoveBytes(); //移动数据到初始位置
                    mReadBuff.ReSize(mReadBuff.Length * 2); //尝试扩充Bytes数组
                }

                socket.BeginReceive(mReadBuff.Bytes, mReadBuff.WriteIndex, mReadBuff.Remain, 0, ReceivedCallBack,
                    socket); //循环接收消息
            }
            catch (SocketException e)
            {
                Debug.LogError("接收服务器消息失败：" + e.ToString());
                Close();
            }
        }

        /// <summary>
        /// 处理接收到的数据包（处理分包粘包产生的问题）
        /// </summary>
        private void OnReceiveData()
        {
            if (mReadBuff.ReadIndex < 0 || mReadBuff.Length < 4) return;

            //解析包头：
            int readIndex = mReadBuff.ReadIndex;
            byte[] receiveData = mReadBuff.Bytes;
            int len = BitConverter.ToInt32(receiveData, readIndex);
            if (mReadBuff.Length < 4 + len) return; //分包
            mReadBuff.ReadIndex += 4; //移动 readIndex
            //解析协议名：
            int nameLen = 0;
            ProtoEnum protocol = MsgTools.DecodeName(mReadBuff.Bytes, mReadBuff.ReadIndex, out nameLen);
            if (protocol == ProtoEnum.None)
            {
                Debug.LogError("处理从服务器接收的数据出错：协议名解析失败");
                Close();
                return;
            }

            mReadBuff.ReadIndex += nameLen; //移动 readIndex
            //解析协议体：
            int bodyLen = len - nameLen;
            try
            {
                //解析：
                MsgBase msg = MsgTools.Decode(protocol, mReadBuff.Bytes, mReadBuff.ReadIndex, bodyLen);
                if (msg == null)
                {
                    Debug.LogError("处理从服务器接收的数据出错：协议体解析失败");
                    Close();
                    return;
                }

                mReadBuff.ReadIndex += bodyLen;
                mReadBuff.CheckAndMoveBytes();
                //处理解析好的消息（加入消息列表）：
                lock (mMsgQue)
                {
                    mMsgQue.Enqueue(msg);
                }

                mMsgCount++;
                if (mReadBuff.Length > 4) //粘包
                {
                    OnReceiveData();
                }
            }
            catch (Exception e)
            {
                Debug.LogError("处理从服务器接收的数据出错：协议体解析失败，Exception：" + e.ToString());
                Close();
            }
        }

        /// <summary>
        /// 设置秘钥
        /// </summary>
        public void SetSecretKey(string key)
        {
            SecretKey = key;
        }

        /// <summary>
        /// 关闭与服务器的连接
        /// </summary>
        /// <param 是否是正常关闭="normal"></param>
        public void Close(bool normal = true)
        {
            if (mSocket == null || mIsConnecting) return;
            if (mIsConnecting) return;
            if (mWriteQue.Count > 0) //需要等待所有消息发送完后，才关闭连接
            {
                mIsClosing = true;
            }
            else
            {
                RealClose(normal);
            }
        }

        /// <summary>
        /// 真正关闭与服务器的连接
        /// </summary>
        private void RealClose(bool normal = true)
        {
            SecretKey = string.Empty;
            //关闭Socket
            mSocket.Close();
            //关闭线程
            if (mHeartThread != null && mHeartThread.IsAlive)
            {
                mHeartThread.Abort();
                mHeartThread = null;
            }

            if (mMsgThread != null && mMsgThread.IsAlive)
            {
                mMsgThread.Abort();
                mMsgThread = null;
            }

            //发送关闭事件
            FirstEvent(NetEvent.Close, normal.ToString());
            mIsBreakConnect = true;
            Debug.Log("Close Socket");
        }

        /// <summary>
        /// 向服务器发送消息
        /// </summary>
        public void SendMsg(MsgBase msg)
        {
            if (mSocket == null || mSocket.Connected == false) return;
            if (mIsConnecting)
            {
                Debug.LogError("正在连接服务器，发送消息失败");
                return;
            }

            if (mIsClosing)
            {
                Debug.LogError("正在关闭服务器连接，发送消息失败");
                return;
            }

            try
            {
                //编码：
                byte[] nameBytes = MsgTools.EncodeName(msg); //协议名
                byte[] bodyBytes = MsgTools.Encode(msg); //协议体
                int len = nameBytes.Length + bodyBytes.Length;
                byte[] headBytes = BitConverter.GetBytes(len); //协议头
                //拼接：
                byte[] sendBytes = new byte[headBytes.Length + len];
                Array.Copy(headBytes, 0, sendBytes, 0, headBytes.Length);
                Array.Copy(nameBytes, 0, sendBytes, headBytes.Length, nameBytes.Length);
                Array.Copy(bodyBytes, 0, sendBytes, headBytes.Length + nameBytes.Length, bodyBytes.Length);
                //将发送的消息的数据包装成ByteArray
                ByteArray byteArray = new ByteArray(sendBytes);
                //加入写入队列
                int count = 0;
                lock (mWriteQue)
                {
                    mWriteQue.Enqueue(byteArray);
                    count = mWriteQue.Count;
                }

                if (count == 1) //发送消息
                {
                    mSocket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallBack, mSocket);
                }
            }
            catch (Exception e)
            {
                Debug.LogError("向服务器发送消息失败，Exception：" + e.ToString());
            }
        }

        /// <summary>
        /// 异步发送消息的回调
        /// </summary>
        private void SendCallBack(IAsyncResult ar)
        {
            try
            {
                Socket socket = (Socket) ar.AsyncState;
                if (socket == null || socket.Connected == false) return;
                //判断发送的数据是否完整：
                int count = socket.EndSend(ar); //发送的数据长度
                ByteArray byteArray = null;
                lock (mWriteQue)
                {
                    byteArray = mWriteQue.Peek();
                }

                byteArray.ReadIndex += count; //移动 readIndex
                if (byteArray.Length == 0) //发送数据完整
                {
                    lock (mWriteQue)
                    {
                        mWriteQue.Dequeue(); //出队
                        if (mWriteQue.Count > 0) //写入队列里还有数据
                        {
                            byteArray = mWriteQue.Peek();
                        }
                        else
                        {
                            byteArray = null;
                        }
                    }
                }

                //发送的数据不完整 or 写入队列中有新数据待发送
                if (byteArray != null)
                {
                    socket.BeginSend(byteArray.Bytes, byteArray.ReadIndex, byteArray.Length, 0, SendCallBack, socket);
                }
                //所有消息发送完毕后，再关闭与服务器的连接
                else if (mIsClosing)
                {
                    RealClose();
                }
            }
            catch (SocketException e)
            {
                Debug.LogError("BeginSend SendCallBack Error：" + e.ToString());
                Close();
            }
        }

        #region Thread

        /// <summary>
        /// 总体处理消息的线程
        /// </summary>
        private void MsgThread()
        {
            while (mSocket != null && mSocket.Connected) //正常连接
            {
                if (mMsgQue.Count <= 0) continue;
                //从消息列表中取消息
                MsgBase msg = null;
                lock (mMsgQue)
                {
                    if (mMsgQue.Count > 0)
                    {
                        msg = mMsgQue.Dequeue();
                    }
                }

                //消息取出
                if (msg != null)
                {
                    if (msg is MsgPing) //心跳包
                    {
                        mLastPongTime = GetTimeStamp(); //记录接收心跳包消息的时间
                        Debug.Log(GetNowTime().ToString() + "接收到心跳包！");
                        mMsgCount--;
                    }
                    else //Unity消息
                    {
                        lock (mUnityMsgQue)
                        {
                            mUnityMsgQue.Enqueue(msg);
                        }
                    }
                }
                else
                {
                    break;
                }
            }
        }

        /// <summary>
        /// 处理心跳包的线程
        /// </summary>
        private void HeartThread()
        {
            while (mSocket != null && mSocket.Connected)
            {
                long now = GetTimeStamp();
                if (now - mLastPingTime > mPingTimeInterval) //每过一段时间发送一次心跳包
                {
                    MsgPing msg = new MsgPing();
                    SendMsg(msg);
                    mLastPingTime = GetTimeStamp();
                }

                //非正常关闭：心跳包超时
                if (now - mLastPongTime > mPingTimeInterval * mPingTimeoutRatio)
                {
                    Close(false);
                }
            }
        }

        #endregion


        #region Helper Function

        /// <summary>
        /// 获取当前的时间戳
        /// </summary>
        public static long GetTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds);
        }

        /// <summary>
        /// 获取当前时间（xx时xx分xx秒）
        /// </summary>
        public static DateTime GetNowTime()
        {
            return DateTime.Now;
        }

        #endregion
    }
}